#include <stdlib.h>
#include <string.h>

#include <errno.h>

#include <common/macro/build.h>
#include <common/macro/debug.h>
#include <common/filelock.h>
#include <common/util.h>

#include <storage/fsm.h>
#include <storage/storage.h>

API_LIBLOCAL storage_t *storage_open(const char *basename, page_size_t page_size) {
	char *fn;
	storage_t *storage;

	storage = malloc(sizeof(*storage));

	storage->page_size = page_size;
	storage->dirty = 0;

	fn = util_strmerge(basename, ".data", NULL);
	storage->pages = io_open_rwc(fn);
	free(fn);

	fn = util_strmerge(basename, ".fsm", NULL);
	storage->fsm = io_open_rwc(fn);
	free(fn);

	return storage;
}

API_LIBLOCAL void storage_close(storage_t *storage) {
	if (storage == NULL) return;

	io_close(storage->pages);
	io_close(storage->fsm);
	free(storage);
}

API_LIBLOCAL int storage_remove(const char *basename) {
	char *fn;
	int rv1, rv2;

	fn = util_strmerge(basename, ".data", NULL);
	rv1 = io_remove(fn);
	free(fn);

	fn = util_strmerge(basename, ".fsm", NULL);
	rv2 = io_remove(fn);
	free(fn);

	/*
	 * We do this, because we want to carry on with removing 
	 * files even in case of a partial failure.
	 */
	if (rv1 != 0) {
		return rv1;
	 } else if (rv2 != 0) {
		return rv2;
	}
	return 0;
}

API_LIBLOCAL int storage_fsm_readlock(storage_t *storage, page_number_t page_number, page_number_t page_count) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg(" READLOCK(FSM): start: %u, size: %u (end: %u) ", fsm_item_size() * page_number, fsm_item_size() * page_count, fsm_item_size() * page_number + fsm_item_size() * page_count);
	/*printtime();*/
	rv =  filelock_readlock(storage->fsm, fsm_item_size() * page_number, fsm_item_size() * page_count);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_fsm_writelock(storage_t *storage, page_number_t page_number, page_number_t page_count) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("WRITELOCK(FSM): start: %u, size: %u (end: %u) ", fsm_item_size() * page_number, fsm_item_size() * page_count, fsm_item_size() * page_number + fsm_item_size() * page_count);
	/*printtime();*/
	rv =  filelock_writelock(storage->fsm, fsm_item_size() * page_number, fsm_item_size() * page_count);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_fsm_unlock(storage_t *storage, page_number_t page_number, page_number_t page_count) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("   UNLOCK(FSM): start: %u, size: %u (end: %u) ", fsm_item_size() * page_number, fsm_item_size() * page_count, fsm_item_size() * page_number + fsm_item_size() * page_count);
	/*printtime();*/
	rv =  filelock_unlock(storage->fsm, fsm_item_size() * page_number, fsm_item_size() * page_count);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_readlock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg(" READLOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size, 1, page_number * storage->page_size + 1);
	/*printtime();*/
	rv =  filelock_readlock(storage->pages, page_number * storage->page_size, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_writelock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("WRITELOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size, 1, page_number * storage->page_size + 1);
	/*printtime();*/
	rv =  filelock_writelock(storage->pages, page_number * storage->page_size, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_rwunlock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("   UNLOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size, 1, page_number * storage->page_size + 1);
	/*printtime();*/
	rv =  filelock_unlock(storage->pages, page_number * storage->page_size, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_antivacuumlock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg(" READLOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size + 1, 1, page_number * storage->page_size + 2);
	/*printtime();*/
	rv =  filelock_readlock(storage->pages, page_number * storage->page_size + 1, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_vacuumlock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("WRITELOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size + 1, 1, page_number * storage->page_size + 2);
	/*printtime();*/
	rv =  filelock_writelock(storage->pages, page_number * storage->page_size + 1, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_vacuumunlock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("   UNLOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size + 1, 1, page_number * storage->page_size + 2);
	/*printtime();*/
	rv =  filelock_unlock(storage->pages, page_number * storage->page_size + 1, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_vacuumchecklock(storage_t *storage, page_number_t page_number) {
	int rv;
	assert(storage != NULL);

	errno = 0;
	debugmsg("   UNLOCK(PAGE): start: %u, size: %u (end: %u) ", page_number * storage->page_size + 1, 1, page_number * storage->page_size + 2);
	/*printtime();*/
	rv = filelock_checklock(storage->pages, page_number * storage->page_size + 1, 1);
	debugmsg("RV=%d : %s\n", rv, strerror(errno));
	/*printtime();*/
	return rv;
}

API_LIBLOCAL int storage_page_read(storage_t *storage, page_number_t page_number, page_t **page) {
	char *buf;
	int rv;

	assert(storage != NULL);

	buf = malloc(storage->page_size);

	io_seek(storage->pages, page_number * storage->page_size, SEEK_SET);
	if (io_read(buf, storage->page_size, 1, storage->pages) != 1) {
		free(buf);
		return -1;
	}

	rv = 0;
	if (page_deserialize(page, buf, storage->page_size) == -1) {
		free(buf);
		rv = -1;
	}
	
	return rv;
}

API_LIBLOCAL int storage_page_write(storage_t *storage, page_number_t page_number, page_t *page) {
	char *buf;
	int rv;

	assert(storage != NULL);
	assert(page != NULL);

	buf = malloc(storage->page_size);

	memset(buf, 0, storage->page_size);
	if (page_serialize(page, buf, storage->page_size) == -1) {
		debugmsg("Unable to serialize page.\n");
		free(buf);
		return -1;
	}

	if (io_seek(storage->pages, page_number * storage->page_size, SEEK_SET) != 0) {
		debugmsg("Unable to seek to destination.\n");
		free(buf);
		return -1;
	}

	rv = (io_write(buf, storage->page_size, 1, storage->pages) == 1 ? 0 : -1);
	free(buf);

	storage->dirty = 1;
	return rv;
}

API_FILELOCAL int storage_fsm_get(storage_t *storage, page_number_t page_number, page_type_e *type, page_size_t *size) {
	fsm_t *fsm;

	fsm_seek_to_page(storage->fsm, page_number);

	debugmsg("Deserializing.\n");
	fsm = fsm_deserialize_file(storage->fsm, 1);
	if (fsm->items_c != 1) {
		debugmsg("ERROR.\n");
		storage_fsm_unlock(storage, page_number, 1);
		fsm_destroy(fsm);
		return -1;
	}
	debugmsg("Success. Turning into RV: %u\n", fsm->items[0].page_size);

	*size = fsm->items[0].page_size;
	*type = fsm->items[0].page_type;
	fsm_destroy(fsm);

	return 0;
}

API_LIBLOCAL int storage_fsm_get_info(storage_t *storage, page_number_t page_number, page_type_e *type, page_size_t *size) {
	int rv;

	debugmsg("Small lock.\n");
	if (storage_fsm_readlock(storage, page_number, 1) != 0)
		return -1;

	rv = storage_fsm_get(storage, page_number, type, size);

	if (rv == 0) {
		if (storage_fsm_unlock(storage, page_number, 1) != 0)
			return -1;
	}

	return rv;
}

API_LIBLOCAL int storage_fsm_lease_new(storage_t *storage, page_number_t *page_number) {
	storage_fsm_lease_find(storage, storage->page_size, PAGE_ANY, page_number, NULL);
	return 0;
}

API_LIBLOCAL int storage_fsm_lease_find(storage_t *storage, page_size_t request, page_type_e type, page_number_t *page_number, page_number_t *skip_page_number) {
	int blocksize;
	fsm_t *fsm;
	page_number_t offset;
	page_number_t skip_item, *skip_item_ptr;

	offset = *page_number;
	blocksize = 1024;
	skip_item_ptr = NULL;

	/* Exit point is the io_feof(storage->fsm) condition. */
	fsm_seek_to_page(storage->fsm, *page_number);
	while (1) {
		debugmsg("Beggining locking.\n");

		/* We take a lease on FSM, which means we write. */
		if (storage_fsm_writelock(storage, offset, blocksize) != 0)
			return -1;

		fsm = fsm_deserialize_file(storage->fsm, blocksize);
		if (fsm == NULL) {
			/* We don't care about unlock failing, we are bailing out with failure. */
			storage_fsm_unlock(storage, offset, blocksize);
			return -1;
		}

		if (fsm->items_c > 0)
			debugmsg("Request %u over %u\n", request, fsm->items[0].page_size);
		else 
			debugmsg("Empty FSM.\n");

		if (skip_page_number != NULL) {
			if (*skip_page_number >= offset && *skip_page_number < offset + fsm->items_c) {
				skip_item = *skip_page_number - offset;
				skip_item_ptr = &skip_item;
			} else {
				skip_item_ptr = NULL;
			}
		}

		if (fsm_get_fitting_page(fsm, request, type, page_number, skip_item_ptr) == 0) {
			/* Shrink the lock to the lease only. */
			debugmsg("Offering: %u (%u).\n", (offset + *page_number), fsm->items[*page_number].page_size);
			fsm_destroy(fsm);
	
			*page_number += offset;
	
			/* Mark the page as leased. */
			storage_fsm_update(storage, *page_number, PAGE_LEASE, 0);
			storage_page_writelock(storage, *page_number);
			storage_fsm_unlock(storage, offset, blocksize); 
	
			debugmsg("Finished locking.\n");
			return 0;
		}

		if (io_feof(storage->fsm))
			break;

		if (storage_fsm_unlock(storage, offset, blocksize) != 0)
			return -1;

		offset += fsm->items_c;
		fsm_destroy(fsm);
	}

	/* 
	 * Nothing was found. Don't give up! Return the following page. If page_number
	 * remained 0, it means there was nothing at all.
	 */
	debugmsg("A new page given.\n");
	*page_number = fsm->items_c;
	*page_number += offset;
	fsm_destroy(fsm);

	storage_fsm_update(storage, *page_number, PAGE_LEASE, 0);
	storage_page_writelock(storage, *page_number);
	storage_fsm_unlock(storage, offset, blocksize); 


	debugmsg("New page: %u\n", *page_number);
	return 1;
}

API_LIBLOCAL int storage_fsm_lease_return(storage_t *storage, page_number_t page_number, page_type_e type, page_size_t newsize) {
	storage_fsm_writelock(storage, page_number, 1);
	storage_fsm_update(storage, page_number, type, newsize);
	storage_page_rwunlock(storage, page_number);
	storage_fsm_unlock(storage, page_number, 1);

	return 0;
}

API_LIBLOCAL int storage_fsm_update(storage_t *storage, page_number_t page_number, page_type_e type, page_size_t newsize) {
	fsm_t *fsm;

	fsm = fsm_new();
	fsm_set_page(fsm, 0, type, newsize);

	debugmsg("Writing new size for %u = %u.\n", page_number, newsize);
	/*printtime();*/

	if (fsm_seek_to_page(storage->fsm, page_number) != 0) {
		fsm_destroy(fsm);
		return -1;
	}

	if (fsm_serialize_file(fsm, storage->fsm) != 0) {
		fsm_destroy(fsm);
		return -1;
	}

	fsm_destroy(fsm);

	storage->dirty = 1;

	debugmsg("Write is DONE\n");

	return 0;
}

API_LIBLOCAL void storage_fsync(storage_t *storage) {
	assert(storage != NULL);

	if (storage->dirty == 0) return;

	io_sync(storage->fsm);
	io_sync(storage->pages);
}
